#include "DiagramFam.h"
#include "../model/UMLBox.h"
#include "../model/UMLLine.h"
#include "WriteVisitor.h"

#include <stdexcept>
#include <QFileInfo>
#include <QDebug>

using std::runtime_error;

DiagramFam::DiagramFam(UMLDiagram* diagram, QWidget* canvasParent)
:	_diagram(diagram),
	_canvas(diagram, canvasParent),
	// _path defaults to empty, perfect
	_name("[no name]")
{
}

DiagramFam::DiagramFam(const QString& path, QWidget* canvasParent)
:	_diagram(new UMLDiagram()),
	_canvas(_diagram.get(), canvasParent),
	_path(path),
	_name(QFileInfo(path).completeBaseName()) // complete for ".foo.xml"
{
	read();
}

UMLDiagram& DiagramFam::diagram()
{
	return *_diagram;
}

UMLCanvas& DiagramFam::canvas()
{
	return _canvas;
}

Acceptor& DiagramFam::acceptor()
{
	return _acceptor;
}

const QString& DiagramFam::path() const
{
	return _path;
}

void DiagramFam::setPath(const QString& path)
{
	_path = path;
	_name = QFileInfo(path).completeBaseName(); // complete for ".foo.xml"
}

const QString& DiagramFam::name() const
{
	return _name;
}

void DiagramFam::save() const
{
	qDebug() << "Saving to " << _path;

	QFile file(_path);
	file.open(QIODevice::WriteOnly);

	QXmlStreamWriter writer(&file);
	writer.setAutoFormatting(true); // make human-readable

	writer.writeStartDocument();
	writer.writeStartElement(WriteVisitor::_tagBumlf);

	WriteVisitor v(writer);

	for (UMLDiagram::iterator iter = _diagram->begin(); iter != _diagram->end();
			++iter)
		(*iter)->accept(v);

	writer.writeEndElement();
	writer.writeEndDocument();
}

const QString WriteVisitor::_tagBumlf = QString("bumlf");
const QString WriteVisitor::_tagBox = QString("box");
const QString WriteVisitor::_tagLine = QString("line");
const QString WriteVisitor::_tagPoint = QString("point");
const QString WriteVisitor::_tagSize = QString("size");
const QString WriteVisitor::_tagProperties = QString("properties");
const QString WriteVisitor::_tagVertices = QString("vertices");

void DiagramFam::read()
{
	qDebug() << "Reading from " << _path;

	QFile file(_path);
	file.open(QIODevice::ReadOnly);

	if (file.atEnd())
		throw std::runtime_error("Empty file.");

	QXmlStreamReader reader(&file);

	while (!reader.atEnd())
	{
		QXmlStreamReader::TokenType type = reader.readNext();

		switch (type)
		{
			case QXmlStreamReader::EndDocument:
				qDebug("Got end of XML document, returning.");
				return;

			case QXmlStreamReader::EndElement:
				// return when we hit </bumlf>
				return;

			case QXmlStreamReader::StartElement:
				if (reader.name() == WriteVisitor::_tagBumlf)
				{
					readBumlf(reader);
					break;
				}
				// follow through to default if start tag unhandled

			default:
				processDefault(reader);
		}
	}
}

void DiagramFam::processDefault(QXmlStreamReader& reader)
{
	switch (reader.tokenType())
	{
		case QXmlStreamReader::NoToken:
			throw std::logic_error("Haven't read anything yet.");

		case QXmlStreamReader::Invalid:
		case QXmlStreamReader::EntityReference:
			qWarning() << "Bad XML file." << reader.errorString();
			throw std::runtime_error("Bad diagram: malformed XML.");

		case QXmlStreamReader::Characters:
			if (!reader.isWhitespace())
				throw runtime_error("Bad diagram: misplaced CDATA.");

		case QXmlStreamReader::StartDocument:
		case QXmlStreamReader::Comment:
		case QXmlStreamReader::ProcessingInstruction:
			// ignored
			break;

		case QXmlStreamReader::EndDocument:
			throw std::runtime_error("Early end of document.");
			return;

		case QXmlStreamReader::StartElement:
		case QXmlStreamReader::EndElement:
			qDebug() << "Bad element " << reader.name();
			throw std::runtime_error("Unrecognized element.");
			break;

		case QXmlStreamReader::DTD:
			qWarning("Ignoring DTD in XML file.");
			break;
	}
}

void DiagramFam::readBumlf(QXmlStreamReader& reader)
{
	while (!reader.atEnd())
	{
		QXmlStreamReader::TokenType type = reader.readNext();

		switch (type)
		{
			case QXmlStreamReader::EndElement:
				if (reader.name() == WriteVisitor::_tagBumlf)
					return;

			case QXmlStreamReader::StartElement:
				if (reader.name() == WriteVisitor::_tagBox)
				{
					UMLBox box = readBox(reader);
					_diagram->add(box);
					break;
				}
				else if (reader.name() == WriteVisitor::_tagLine)
				{
					UMLLine line = readLine(reader);
					_diagram->add(line);
					break;
				}
				// follow through to default if start tag unhandled

			default:
				processDefault(reader);
		}
	}
}

UMLBox DiagramFam::readBox(QXmlStreamReader& reader)
{
	qDebug("Reading <box>.");

	UMLBox box;

	while (!reader.atEnd())
	{
		QXmlStreamReader::TokenType type = reader.readNext();

		switch (type)
		{
			case QXmlStreamReader::EndElement:
				if (reader.name() == WriteVisitor::_tagBox)
					goto Return;

			case QXmlStreamReader::StartElement:
				if (reader.name() == WriteVisitor::_tagPoint)
				{
					box.setPosition(readPoint(reader));
					break;
				}
				else if (reader.name() == WriteVisitor::_tagSize)
				{
					box.setSize(readSize(reader));
					break;
				}
				else if (reader.name() == WriteVisitor::_tagProperties)
				{
					box.setText(readProperties(reader));
					break;
				}
				// follow through to default if start tag unhandled

			default:
				processDefault(reader);
		}
	}

Return:
	return box;
}

UMLLine DiagramFam::readLine(QXmlStreamReader& reader)
{
	qDebug("Reading <line>.");

	std::vector<QPoint> vertices;
	BUMLSText text;

	while (!reader.atEnd())
	{
		QXmlStreamReader::TokenType type = reader.readNext();

		switch (type)
		{
			case QXmlStreamReader::EndElement:
				if (reader.name() == WriteVisitor::_tagLine)
					goto Return;

			case QXmlStreamReader::StartElement:
				if (reader.name() == WriteVisitor::_tagVertices)
				{
					vertices = readVertices(reader);
					break;
				}
				else if (reader.name() == WriteVisitor::_tagProperties)
				{
					text = readProperties(reader);
					break;
				}
				// follow through to default if start tag unhandled

			default:
				processDefault(reader);
		}
	}

Return:
	return UMLLine(vertices, text);
}

std::vector<QPoint> DiagramFam::readVertices(QXmlStreamReader& reader)
{
	/* <vertices> is an ordered list of <point>s */

	std::vector<QPoint> vertices;

	while (!reader.atEnd())
	{
		QXmlStreamReader::TokenType type = reader.readNext();

		switch (type)
		{
			case QXmlStreamReader::EndElement:
				if (reader.name() == WriteVisitor::_tagVertices)
					goto Return;

			case QXmlStreamReader::StartElement:
				if (reader.name() == WriteVisitor::_tagPoint)
				{
					vertices.push_back(readPoint(reader));
					break;
				}
				// follow through to default if start tag unhandled

			default:
				processDefault(reader);
		}
	}

Return:

	return vertices;
}

QPoint DiagramFam::readPoint(QXmlStreamReader& reader)
{
	qDebug("Reading <point>.");

	QString sx = reader.attributes().value("x").toString();
	QString sy = reader.attributes().value("y").toString();
	
	bool good;

	int x = sx.toInt(&good);
	if (!good)
		throw runtime_error("Bad x-attribute in <point>.");

	int y = sy.toInt(&good);
	if (!good)
		throw runtime_error("Bad y-attribute in <point>.");

	// Verify that we have an empty element.
	if (reader.readNext() != QXmlStreamReader::EndElement ||
			reader.name() != WriteVisitor::_tagPoint)
		throw runtime_error("Malformed <point> tag.");

	return QPoint(x, y);
}

QSize DiagramFam::readSize(QXmlStreamReader& reader)
{
	QString sw = reader.attributes().value("width").toString();
	QString sh = reader.attributes().value("height").toString();
	
	bool good;

	int width = sw.toInt(&good);
	if (!good)
		throw runtime_error("Bad w-attribute in <size>.");

	int height = sh.toInt(&good);
	if (!good)
		throw runtime_error("Bad h-attribute in <size>.");

	// Verify that we have an empty element.
	if (reader.readNext() != QXmlStreamReader::EndElement ||
			reader.name() != WriteVisitor::_tagSize)
		throw runtime_error("Malformed <size> tag.");

	return QSize(width, height);
}

BUMLSText DiagramFam::readProperties(QXmlStreamReader& reader)
{
	/* <properties> should just contain string data */

	if (reader.readNext() != QXmlStreamReader::Characters)
		throw runtime_error("Malformed <properties> tag.");

	QString text = reader.text().toString();

	if (reader.readNext() != QXmlStreamReader::EndElement ||
			reader.name() != WriteVisitor::_tagProperties)
		throw runtime_error("Malformed <properties> tag.");

	return BUMLSText(text);
}

WriteVisitor::WriteVisitor(QXmlStreamWriter& writer)
:	_writer(writer)
{
}

void WriteVisitor::visit(UMLBox& box)
{
	_writer.writeStartElement(_tagBox);

	write(_writer, box.position());
	write(_writer, box.rect().size());
	write(_writer, box.text());

	_writer.writeEndElement();
}

void WriteVisitor::visit(UMLLine& line)
{
	_writer.writeStartElement(_tagLine);

	/* Write Vertices */

	_writer.writeStartElement(_tagVertices);

	for (UMLLine::size_type i = 0; i != line.vertexCount(); ++i)
		write(_writer, line.vertex(i));

	_writer.writeEndElement();

	/* Write properties */

	write(_writer, line.text());

	_writer.writeEndElement();
}

void WriteVisitor::write(QXmlStreamWriter& writer, const QPoint& pt)
{
	writer.writeStartElement(_tagPoint);

	writer.writeAttribute("x", QString::number(pt.x()));
	writer.writeAttribute("y", QString::number(pt.y()));

	writer.writeEndElement();
}

void WriteVisitor::write(QXmlStreamWriter& writer, const QSize& size)
{
	writer.writeStartElement(_tagSize);

	writer.writeAttribute("width", QString::number(size.width()));
	writer.writeAttribute("height", QString::number(size.height()));

	writer.writeEndElement();
}

void WriteVisitor::write(QXmlStreamWriter& writer, const BUMLSText& text)
{
	writer.writeTextElement(_tagProperties, text.fullText());
}

